//=================================================================
//
// Renderer.cpp
//
// Written by Luke Olsen, Jan. 2004
// Based on example code distributed with Qt under GPL license.
//
// Provides a bare-bones implementation of a GL widget under Qt.
// Code stubs are provided; have to add code to draw to the
// screen using OpenGL API calls.
//
//=================================================================
//***************************************************************
//
// Created BY: JESSE SAHOTA
// Assignment: 03
// COURSE: CPSC 453 001
//
//***************************************************************
#include "renderer.h"
#include <iostream>
#include <ctime>

using namespace std;
//################################################################
//Part 1: MAIN OPENGL FUNCTIONS
//################################################################
//=================================================================
// Constructor: Initialize class variables, etc.
//=================================================================
Renderer::Renderer( QWidget* parent )
: QGLWidget( parent )
{
    // Initialize variables here
    cameraZ = 1.0;
    dist = 5;//might put it back to 3 and reduce the size into 3
    modelYRotation = 0;
    isOrthogonal = false;
    // Set up the timer, connect it to the animation slot
    srand(time(NULL));
    timer = new QTimer( this );
    connect(timer, SIGNAL(timeout()), this, SLOT(animate()));
    
    
}

//=================================================================
// Destructor: Free memory, delete glLists, etc.
//=================================================================
Renderer::~Renderer()
{
    delete timer;
}

//=================================================================
// PaintGL(): Draw to the screen using OpenGL API commands
//=================================================================
void Renderer::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    
    // Place camera
    gluLookAt(0.0, 0.0, dist* cameraZ, // Eye/camera position
              0.0, 0.0, 0.0,     // Look-at position
              0.0, 1.0, 0.0);    // "Up" vector
    
    glPushMatrix();
    
    tracker.applyRotation();
    
    glScaled(2,2,2);
    plant.draw();
    //drawTestStub();
    /*
     //---TESTING
     glLineWidth(6);
     glBegin(GL_LINE_LOOP);
     glColor3f(1.0, 0.0, 0.0); //Red
     glVertex3f(-0.5, -0.5,0);
     glColor3f(0.0, 1.0, 0.0); //Green
     glVertex3f(-0.5, 0.5,0);
     glColor3f(0.0, 0.0, 1.0); //Blue
     glVertex3f( 0.5, 0.5, 0);
     glColor3f(1.0, 1.0, 1.0); //White
     glVertex3f( 0.5, -0.5,0);
     
     glEnd();
     //----Testing*/
    
    glFlush();
}

//=================================================================
// initializeGL(): Set up the OpenGL rendering state,
//    define lights, call glEnable()s, load textures, etc.
//=================================================================
void Renderer::initializeGL()
{
    glClearColor(0, 0, 0, 1);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    lighting();
}

//do lighting code here
//****************************************************************
//Function Name: lighting
//
//Purpose: To setup the lighting of the scene
//Input: None
//Output: None
//
//****************************************************************
void Renderer::lighting(){
    // enable lighting
    glEnable( GL_LIGHTING );
    
    // enable use of glColor() to specify ambient & diffuse material properties
    glEnable( GL_COLOR_MATERIAL );
    glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );
    
    // set some common light & material properties, which don't have to
    // be re-specified later
    
    GLfloat ambientLight[] = { 0.1, 0.1, 0.1, 1.0 };
    glLightfv( GL_LIGHT0, GL_AMBIENT, ambientLight );
    GLfloat specularLight[] = { 1.0, 1.0, 1.0, 1.0 };
    glLightfv( GL_LIGHT0, GL_SPECULAR, specularLight );
    GLfloat specularColor[] = { 0.7, 0.7, 0.7, 1.0 };
    glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, specularColor );
    GLfloat shininess[] = { 90.0 };
    glMaterialfv( GL_FRONT_AND_BACK, GL_SHININESS, shininess );
    GLfloat lightDir[] = { 10.0, 10.0, 10.0, 0.0 };
    glLightfv( GL_LIGHT0, GL_POSITION, lightDir );
    
    //turn on the light
    glEnable( GL_LIGHT0);
    glEnable(GL_NORMALIZE);
}
//=================================================================
// resizeGL(): Set up the OpenGL viewport (frustum or ortho)
//=================================================================
void Renderer::resizeGL( int w, int h )
{
    // Switch to the propper matrix
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    
    // Set drawing to take up the entire window
    glViewport(0, 0, w, h);
    
    width = w;
    height = h;
    
    double ratio;
    if ( w > h ) // In this case the w/h ratio is > 1
    {
        ratio = (double)w/(double)h;
        if(isOrthogonal){
            glOrtho( -ratio, ratio, -1, 1,-200,200);
        }else{
            gluPerspective( 60, double(w)/h, 0.1, 200);
        }
    }
    else        // In this case the h/w ratio is > 1
    {
        ratio = (double)h/(double)w;
        if(isOrthogonal){
            glOrtho( -ratio, ratio, -1, 1,-200,200);
        }else{
            gluPerspective( 60, double(w)/h, 0.1, 200);
        }
    }
    
    //Switch back to modelview matrix
    glMatrixMode(GL_MODELVIEW);
}
//################################################################
//Part 2: CLASS SLOTS
//################################################################
//=================================================================
// animate(): Perform animation-related updates whenever the
//            timer event fires
//=================================================================
void Renderer::animate()
{
    // Update animation-related variables
    
    // Request display redraw
    updateGL();
}

void Renderer::tropismTreeModel(int coefficent)
{
    
    plant.setTropismFactor(coefficent/20.0);
    tropLabel -> setText("Tropism coefficent: " + QString::number(plant.getTropismFactor()));
    updateGL();
    
}

void Renderer::simulatePlant(){
    plant.grow();
    updateGL();
}

void Renderer::resetTrackball(){
    tracker.indentityMatrix();
    updateGL();
}

void Renderer::savePoints(){
    QMessageBox msgBox;
    QString name = QFileDialog::getSaveFileName(this, tr("Open File"),
                                                "input", tr("Text (*.txt)"));
    if(!name.isEmpty()){
        if(plant.saveFile(name.toStdString())){
            
            msgBox.setText("File " + name + "saved!");
        }else{
            msgBox.setText("Unsuccessful! Either point being used, no points  \n or invalid File");
        }
        msgBox.exec();
    }
    updateGL();//not really needed
}

void Renderer::loadPoints(){
    QString name = QFileDialog::getOpenFileName(this, tr("Open File"),
                                                "input", tr("Text (*.txt)"));
    if(!name.isEmpty()){
        plant.loadFile(name.toStdString());
        //QMessageBox msgBox;
        //msgBox.setText("File " + name + "successfully loaded!");
        //msgBox.exec();
    }
    updateGL();
}

void Renderer::createPoints(){
    plant.generateAttractionPoints();
    updateGL();
}

void Renderer::loadTexture(){
    QImage image;

    QString name = QFileDialog::getOpenFileName(this, tr("Open TexturesFile"),
                                                "input", tr("BMP (*.bmp)"));
    if(name.isEmpty()){
     return;
    }
    image.load(name);
    QImage texture = QGLWidget::convertToGLFormat(image);
    plant.initTexture(texture.height(),texture.width(), texture.bits());
        //plant.loadTexture(name.toStdString());


    updateGL();
}

void Renderer::changeTreeSize(int value){
    plant.setTreeSize(6.0/value);
    plant.update();
    updateGL();
}

void Renderer::changeInfluence(int value){
    plant.setRadiusOfInfl(value/10.0);
    radLabel -> setText("Radius of Influence: " + QString::number(plant.getRadiusOfInfl()));
    updateGL();
}

void Renderer::changeKill(int value){
    plant.setKillDistance(value/20.0);
    killLabel -> setText("Kill Distance: " + QString::number(plant.getKillDistance()));
    updateGL();
}

void Renderer::changeToRegularMode(){
    plant.setTreeType(0);
}

void Renderer::changeToPineMode(){
    plant.setTreeType(1);
}

void Renderer::selectSkeleton(){
    plant.setDrawType(0);
    updateGL();
}

void Renderer::selectWireFrame(){
    plant.setDrawType(1);
    updateGL();
}

void Renderer::selectSmoothShading(){
    plant.setDrawType(2);
    updateGL();
}

void Renderer::changeDisapperTexture(bool clicked){
    plant.setShowTexture(!clicked);
    updateGL();
}

//=================================================================
// Intercept user mouse presses in the GL window
//=================================================================
//=================================================================
//################################################################
//Part 3: MOUSE AND KEYBOARD FUNCTIONS
//################################################################
//****************************************************************
//Function Name: mousePressEvent
//
//Purpose: If mouse was a left click, which is use for trackball
//-------- first it x,y pixel location to x,y world coordinate
//-------- then figures out, if z*z is negative reject
//-------- else get z, save it in vector for later use and normalize it
//Input: e - the current position when the mouse was pressed
//Output: None
//
//****************************************************************
void Renderer::mousePressEvent( QMouseEvent *e )
{
    if(e->buttons() & Qt::LeftButton){
        double worldX = calculateXCoord( e->x());
        double worldY = calculateYCoord( e->y());
        tracker.startTrackball(worldX, worldY);
    }
    updateGL();
}
//****************************************************************
//Function Name: mouseMoveEvent
//
//Purpose: If mouse was a left click, which is use for trackball
//-------- first it x,y pixel location to x,y world coordinate
//-------- then figures out, if z*z is negative reject
//-------- else get z, use it in vector and normalize it
//-------- do cross product with currently calculate vector and
//-------- old vector the saved. Normalize it, get angle of length
//-------- of the product vector that use product of cross product
//-------- Update the rotation matrix of the model, and save the vector
//-------- that calculated for z as old vector.
//
//Input: e - the current position when the mouse was moved
//Output: None
//
//****************************************************************
void Renderer::mouseMoveEvent(QMouseEvent * e){
    if(e->buttons() & Qt::LeftButton){
        double worldX = calculateXCoord( e->x());
        double worldY = calculateYCoord( e->y());
        tracker.trackballMovement(worldX, worldY);
    }
    updateGL();
}
//****************************************************************
//Function Name: wheelEvent
//
//Purpose: Zooms down or up depending how the mouse wheel rolls
//Input: e - the current position when the wheel was rolled
//Output: None
//
//****************************************************************
void Renderer::wheelEvent(QWheelEvent *e){
    if(e->delta() > 0){
        dist = max(0.5, dist - 0.5);
    }else{
        dist = min(10.0, dist + 0.5);
    }
    updateGL();
}
//################################################################
//PART 4: Helper private functions
//###############################################################
//***************************************************
//
//Function Name: calculateXCoord
//
//Input: Int x - X value from the world coordinate
//Return Value: returns the X- point coordinate where the x value was clicked,
//
//Purpose: This calculate the x-pixel world coordinate from the mouse input to the
//-------- point coordinate
//
//****************************************************
double Renderer::calculateXCoord(const int & x){
    
    double temp = (double)x/(double)width;
    double ratio = 1;
    if ( width > height )
        ratio = (double)width/(double)height;
    else
        ratio = 1;
    double result = ((temp*(2.0*ratio))-(1.0*ratio));
    return result;
}

//***************************************************
//
//Function Name: calculateYCoord
//
//Input: Int y - y value from the world coordinate
//Return Value: returns the y- point coordinate where the y value was clicked,
//
//Purpose: This calculate the Y-pixel world coordinate from the mouse input to the
//-------- point coordinate
//
//****************************************************
double Renderer::calculateYCoord(const int & y){
    
    double temp = (double)y/(double)height;
    double ratio;
    if ( width > height )
        ratio = 1;
    else
        ratio = (double)height/(double)width;
    double result = -((temp*(2.0*ratio))-1.0*ratio);
    return result;
}
void Renderer::setLabelTropism(QLabel * tLabel){
    tropLabel = tLabel;
}

void Renderer::setLabelRadius(QLabel * rLabel){
    radLabel = rLabel;
}

void Renderer::setLabelKill(QLabel * kLabel) {
    killLabel = kLabel;
}

void Renderer::drawTestStub(){
    GLUquadric * obj = gluNewQuadric();
    glColor3d(0.835, 0.725, 0.3);
    gluCylinder(obj,0.25, 0.25,0.9,25, 25);
    gluDeleteQuadric(obj);
}
